#include <QFileDialog>
#include <QTime>

#include "gethash.h"
#include "ui_gethash.h"
#include "stribog.h"
#include "stribog_data.h"
#include "test_data.h"

const char* HASH_TITLE = "Hash_512";

GetHash::GetHash(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::GetHash)
{
    ui->setupUi(this);
}

GetHash::~GetHash()
{
    delete ui;
}

void GetHash::on_pushButtonBrowse_clicked()
{
    static QString last_path = QApplication::applicationDirPath();
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open file"), last_path);
    if(!fileName.isEmpty()) {
        ui->lineEditPathToFile->setText(fileName);
        QDir dir(fileName);
        last_path = dir.absoluteFilePath(fileName);
    }
}

void GetHash::on_pushButtonGetHash_clicked()
{
    QString fileName = ui->lineEditPathToFile->text();
    if(fileName.isEmpty()) {
        showInfo("File not selected!");
        return;
    }
    QFile file(fileName);
    if(!file.open(QIODevice::ReadOnly)) {
        showInfo(file.errorString());
        return;
    }
    //hash_[64]={};
    QByteArray data = file.readAll();
    unsigned long long  length = 8*data.length(); // 8* couse length in bits
    unsigned char *data_array = reinterpret_cast<unsigned char *>(data.data());
    QTime start_time = QTime::currentTime();
    hash_512(data_array, length, hash_);
    int seconds = 0 - QTime::currentTime().secsTo(start_time);

    QString str;
    str.append(HASH_TITLE);
    str.append(QString(" calculated. spent time: %1 seconds\n\n").arg(seconds));
    for(int i=0; i<HASH_SIZE; i++) {
        str.append(QString::number(hash_[i], 16));
        str.append(" ");
    }
    showInfo(str);

    file.close();
}

void GetHash::on_pushButtonSelfTest_clicked()
{
    unsigned char h512[64]={}, h256[32] = {};
    int i=0;

    for(i=0;i<TEST_VECTORS;i++)
    {
        hash_512(Message[i],MessageLength[i],h512);

        if(memcmp(h512,Hash_512[i],sizeof(unsigned char)*64))
        {
            showInfo(QString("Self test (hash 512) failed!\nMessage: %1\nLength: %2")
                     .arg(i).arg(MessageLength[i]));
            return;
        }

        hash_256(Message[i],MessageLength[i],h256);

        if(memcmp(h256,Hash_256[i],sizeof(unsigned char)*32))
        {
            showInfo(QString("Self test (hash 512) failed!\nMessage: %1\nLength: %2")
                     .arg(i).arg(MessageLength[i]));
            return;
        }
    }

    showInfo("Self testing successfully passed");
}

void GetHash::showInfo(QString info)
{
    ui->textEditHash->clear();
    ui->textEditHash->setText(info);
}

/* ################################################################################### */

void GetHash::AddModulo512(const unsigned char *a,const unsigned char *b,unsigned char *c)
{
    int i = 0, t = 0;

    for(i=63;i>=0;i--)
    {
        t = a[i] + b[i] + (t >> 8);
        c[i] = t & 0xFF;
    }
}

void GetHash::AddXor512(const void *a,const void *b,void *c)
{
    int i = 0;
    const long long *A=static_cast<const long long *>(a), *B=static_cast<const long long *>(b);
    unsigned long long *C=static_cast<unsigned long long *>(c);

    for(i=0;i<8;i++)
    {
        C[i] = A[i] ^ B[i];
    }
}

void GetHash::S(unsigned char *state)
{
    int i = 0;

    for(i=0;i<64;i++)
    {
        state[i] = Sbox[state[i]];
    }
}

void GetHash::L(unsigned char *state)
{
    unsigned long long v = 0;
    int i = 0, j = 0, k = 0;

    for(i=0;i<8;i++)
    {
        v=0;
        for(k=0;k<8;k++)
        {
            for(j=0;j<8;j++)
            {
                if ((state[i*8+k] & (1<<(7-j))) != 0)
                    v ^= A[k*8+j];
            }
        }
        for(k=0;k<8;k++)
        {
            state[i*8+k] = (v & ((unsigned long long)0xFF << (7-k)*8)) >> (7-k)*8;
        }
    }
}

void GetHash::P(unsigned char *state)
{
    int i = 0;
    unsigned char t[64] = {};

    for(i=0;i<64;i++)
    {
        t[i] = state[Tau[i]];
    }

    memcpy(state,t,64);
}

void GetHash::KeySchedule(unsigned char *K,int i)
{
    AddXor512(K,C[i],K);

    S(K);
    P(K);
    L(K);
}

void GetHash::E(unsigned char *K,const unsigned char *m, unsigned char *state)
{
    int i = 0;

    memcpy(K,K,64);

    AddXor512(m,K,state);

    for(i=0;i<12;i++)
    {
        S(state);
        P(state);
        L(state);
        KeySchedule(K,i);
        AddXor512(state,K,state);
    }
}

void GetHash::g_N(const unsigned char *N,unsigned char *h,const unsigned char *m)
{
    unsigned char t[64], K[64];

    AddXor512(N,h,K);

    S(K);
    P(K);
    L(K);

    E(K,m,t);

    AddXor512(t,h,t);
    AddXor512(t,m,h);
}

void GetHash::hash_X(unsigned char *IV,const unsigned char *message,unsigned long long length,unsigned char *out)
{
    unsigned char v512[64] = {
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00
    };
    unsigned char v0[64] = {
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
    };
    unsigned char Sigma[64] = {
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
    };
    unsigned char N[64] = {
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
    };
    unsigned char m[64], *hash = IV;
    unsigned long long len = length;

    // Stage 2
    while (len >= 512)
    {
        memcpy(m, message + len/8 - 63 - ( (len & 0x7) == 0 ), 64);

        g_N(N,hash,m);
        AddModulo512(N,v512,N);
        AddModulo512(Sigma,m,Sigma);
        len -= 512;
    }

    memset(m,0,64);
    memcpy(m + 63 - len/8 + ( (len & 0x7) == 0 ), message, len/8 + 1 - ( (len & 0x7) == 0 ));

    // Stage 3
    m[ 63 - len/8 ] |= (1 << (len & 0x7));

    g_N(N,hash,m);
    v512[63] = len & 0xFF;
    v512[62] = len >> 8;
    AddModulo512(N,v512,N);

    AddModulo512(Sigma,m,Sigma);

    g_N(v0,hash,N);
    g_N(v0,hash,Sigma);

    memcpy(out, hash, 64);
}

void GetHash::hash_512(const unsigned char *message,unsigned long long length,unsigned char *out)
{
    unsigned char IV[64] =
    {
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
    };

    hash_X(IV,message,length,out);
}

void GetHash::hash_256(const unsigned char *message,unsigned long long length,unsigned char *out)
{
    unsigned char IV[64] =
    {
            0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
            0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
            0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
            0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01
    };
    unsigned char hash[64];

    hash_X(IV,message,length,hash);

    memcpy(out,hash,32);
}


void GetHash::on_pushButtonSave_clicked()
{
    QString text = ui->textEditHash->toPlainText();
    if(text.startsWith(HASH_TITLE)) {
        QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), QDir::homePath());
        if(fileName.isEmpty())
            return;
        QFile file(fileName);
        file.open(QIODevice::WriteOnly);
        if(file.write(reinterpret_cast<const char *>(hash_), HASH_SIZE)) {
            showInfo("Hash saved in file: "+fileName);
        }
        else {
            showInfo("Error: "+file.errorString());
        }
    }
    else {
        showInfo("Warning! Attempt to save incorrect data");
    }
}
